# ~*~ coding: utf-8 ~*~
import json
import os
import uuid
import datetime

from sqlalchemy import func
from flask import views, request, render_template, jsonify

from apps.perms.mixin import LoginRequiredMixin, PermissionRequiredMixin
from apps.common import response
from apps.common.mixin import CompatibleWithSelect2Mixin
from apps.common.utils import fill_date_to_timestamp, timestamp_obj_to_str, list2dict
from apps.extensions import celery, cache, db
from .celery.utils import get_celery_task_log_path
from .models import Task, AdHoc, AdHocRunHistory


__all__ = [
    'CeleryTaskResultAPI', 'CeleryTaskLogAPI', 'CeleryTaskLogView',
    'TasKListAPI', 'TasKListView', 'AdHocListAPI', 'AdHocListView',
    'HistoryListAPI', 'HistoryListView'
]


class CeleryTaskResultAPI(LoginRequiredMixin, views.MethodView):

    def get(self, *args, **kwargs):
        task_id = kwargs.get('pk').__str__()
        async_result = celery.AsyncResult(task_id, backend=celery.backend)

        if async_result.state == 'SUCCESS':
            result = json.dumps({'state': 'SUCCESS', 'result': async_result.get()})
        elif async_result.state == 'PENDING':
            result = json.dumps({'state': 'PENDING', 'result': None})
        elif async_result.state == 'STARTED':
            result = json.dumps({'state': 'STARTED', 'result': None})
        elif async_result.state == 'PENDING':
            result = json.dumps({'state': 'RETRY', 'result': None})
        elif async_result.state == 'FAILURE':
            result = json.dumps({'state': 'FAILURE', 'result': None})
        else:
            result = json.dumps({'state': None, 'result': None})

        return result


class CeleryTaskLogAPI(LoginRequiredMixin, views.MethodView):
    buff_size = 1024 * 10
    end = False
    task = None

    def is_file_finish_write(self):
        return self.task.ready()

    def filter_line(self, line):
        """
        过滤行，可能替换一些信息
        :param line:
        :return:
        """
        return line.replace('*', '')

    def get_no_file_message(self, mark):
        if mark == 'undefined':
            return '.'
        else:
            return 'Waiting task start'

    def read_from_file(self, log_path, mark):
        with open(log_path, 'r', encoding='utf-8') as f:
            offset = cache.get(mark) or 0
            f.seek(offset)
            data = f.read(self.buff_size).replace('\n', '\r\n')

            new_mark = str(uuid.uuid4())
            cache.set(new_mark, f.tell(), 5)

            if data == '' and self.is_file_finish_write():
                self.end = True
            _data = '.'
            for line in data.split('\r\n'):
                new_line = self.filter_line(line)
                if line == '':
                    continue
                _data += '\r\n' + new_line
            return _data, self.end, new_mark

    def get(self, *args, **kwargs):
        task_id = kwargs.get('pk').__str__()
        self.task = celery.AsyncResult(task_id, backend=celery.backend)
        log_path = get_celery_task_log_path(task_id)
        mark = request.args.get("mark") or str(uuid.uuid4())

        if not log_path or not os.path.isfile(log_path):
            message = self.get_no_file_message(mark)
            return response.success(data={"data": message})

        data, end, new_mark = self.read_from_file(log_path, mark)
        return response.success(data={"data": data, 'end': end, 'mark': new_mark})


class CeleryTaskLogView(LoginRequiredMixin, views.MethodView):
    def get(self, *args, **kwargs):
        task_id = kwargs.get('pk').__str__()
        return render_template('ops/celery_task_log.html', task_id=task_id)


class TasKListAPI(CompatibleWithSelect2Mixin, PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'ops-task-list'

    def get(self):
        create_time = request.args.get('create_time')
        start_time = end_time = None
        if create_time:
            start_time = fill_date_to_timestamp('start', create_time.split('~')[0])
            end_time = fill_date_to_timestamp('end', create_time.split('~')[1])

        filter_conditions = {
            'and': [
                ('create_time', 'gte', start_time),
                ('create_time', 'lte', end_time),
            ],
            'or': [
                ('name', 'like', request.args.get('search')),
            ],
        }

        # 排序
        sort = request.args.get('sort') or 'create_time'
        order = request.args.get('order') or 'desc'
        order_by_list = [(sort, order), ]

        # 分页
        offset = request.args.get('offset') or 0
        limit = request.args.get('limit') or 12

        # Compatible with select2
        if request.values.get('type') == 'select2':
            filter_conditions, order_by_list, offset, limit = self.select2_queryset()

        tasks, count = Task.get_queryset(filter_conditions=filter_conditions,
                                         order_by_list=order_by_list,
                                         offset=offset, limit=limit)
        task_ids = [task.id for task in tasks]
        # adhoc
        adhoc_counts = db.session.query(AdHoc.task_id, func.count(AdHoc.task_id)). \
            filter(AdHoc.task_id.in_(task_ids)). \
            group_by(AdHoc.task_id). \
            all()
        adhoc_counts = list2dict(adhoc_counts)

        # history
        history_counts_total = db.session.query(AdHocRunHistory.task_id, func.count(AdHocRunHistory.task_id)). \
            filter(AdHocRunHistory.task_id.in_(task_ids)). \
            group_by(AdHocRunHistory.task_id). \
            all()
        history_counts_fail = db.session.query(AdHocRunHistory.task_id, func.count(AdHocRunHistory.task_id)). \
            filter(AdHocRunHistory.task_id.in_(task_ids), AdHocRunHistory.is_finished == 0). \
            group_by(AdHocRunHistory.task_id). \
            all()
        history_counts_success = db.session.query(AdHocRunHistory.task_id, func.count(AdHocRunHistory.task_id)). \
            filter(AdHocRunHistory.task_id.in_(task_ids), AdHocRunHistory.is_finished == 1). \
            group_by(AdHocRunHistory.task_id). \
            all()

        history_counts_total = list2dict(history_counts_total)
        history_counts_fail = list2dict(history_counts_fail)
        history_counts_success = list2dict(history_counts_success)

        # is_success
        stmt = db.session.query(AdHocRunHistory.task_id, AdHocRunHistory.is_success).\
            filter(AdHocRunHistory.task_id.in_(task_ids)).\
            order_by(AdHocRunHistory.date_start.desc()).\
            subquery()  # 子查询
        success_list = db.session.query(stmt.c.task_id, stmt.c.is_success).group_by(stmt.c.task_id).all()
        success_list = list2dict(success_list)

        results = list()
        for task in tasks:
            results.append({
                'id': task.id,
                'name': task.name,
                'history_count': '{}/{}/{}'.format(
                    history_counts_fail.get(task.id, 0),
                    history_counts_success.get(task.id, 0),
                    history_counts_total.get(task.id, 0)
                ),
                'adhoc_count': adhoc_counts.get(task.id),
                'is_success': int(success_list.get(task.id)),
                'create_by': task.create_by,
                'create_time': timestamp_obj_to_str(task.create_time)
            })

        resp = {
            "data": {
                "rows": results,
                "total": count
            }
        }
        return jsonify(resp)


class TasKListView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'ops-task-list'

    def get(self):
        return render_template('ops/task_list.html')


class AdHocListAPI(CompatibleWithSelect2Mixin, PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'ops-task-list'

    def get(self):
        create_time = request.args.get('create_time')
        start_time = end_time = None
        if create_time:
            start_time = fill_date_to_timestamp('start', create_time.split('~')[0])
            end_time = fill_date_to_timestamp('end', create_time.split('~')[1])

        filter_conditions = {
            'and': [
                ('create_time', 'gte', start_time),
                ('create_time', 'lte', end_time),
                ('task_id', 'eq', request.args.get('task')),
            ],
            'or': [
                # ('name', 'like', request.args.get('search')),
            ],
        }

        # 排序
        sort = request.args.get('sort')
        order = request.args.get('order')
        order_by_list = [(sort, order), ]

        # 分页
        offset = request.args.get('offset') or 0
        limit = request.args.get('limit') or 12

        # Compatible with select2
        if request.values.get('type') == 'select2':
            filter_conditions, order_by_list, offset, limit = self.select2_queryset()

        adhocs, count = AdHoc.get_queryset(filter_conditions=filter_conditions,
                                           order_by_list=order_by_list,
                                           offset=offset, limit=limit)
        adhoc_ids = [adhoc.id for adhoc in adhocs]
        task_dict = db.session.query(AdHoc.id, Task.name). \
            join(Task, AdHoc.task_id == Task.id). \
            filter(AdHoc.id.in_(adhoc_ids)). \
            all()
        task_dict = list2dict(task_dict)

        results = list()
        for adhoc in adhocs:
            tasks = [task['name'] for task in adhoc.tasks]
            hosts = [host['hostname'] for host in adhoc.hosts]
            results.append({
                'id': adhoc.id,
                'task_name': task_dict.get(adhoc.id),
                'tasks': '<br>'.join(tasks),
                'hosts': '<br>'.join(hosts),
                'create_time': timestamp_obj_to_str(adhoc.create_time)
            })

        resp = {
            "data": {
                "rows": results,
                "total": count
            }
        }
        return jsonify(resp)


class AdHocListView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'ops-task-list'

    def get(self):
        task = request.args.get('task', default='')
        return render_template('ops/adhoc_list.html', task=task)


class HistoryListAPI(CompatibleWithSelect2Mixin, PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'ops-task-list'

    def get(self):
        create_time = request.args.get('create_time')
        start_time = end_time = None
        if create_time:
            start_time = fill_date_to_timestamp('start', create_time.split('~')[0])
            end_time = fill_date_to_timestamp('end', create_time.split('~')[1])

        filter_conditions = {
            'and': [
                ('date_start', 'gte', start_time),
                ('date_start', 'lte', end_time),
                # ('adhoc_id', 'eq', request.args.get('adhoc')),
                # ('adhoc_id', 'in', adhoc_list),
            ],
            'or': [
                # ('name', 'like', request.args.get('search')),
            ],
        }

        # 传入Adhoc过滤条件： 显示当前版本下的所以记录
        adhoc_id = request.args.get('adhoc')
        if adhoc_id:
            filter_conditions['and'].append(('adhoc_id', 'eq', adhoc_id))
        # 默认过滤参数：只显示最后一次adhoc执行的日志信息
        else:
            # sql先order by，再group by。如果需要先group by在进行order by使用子查询。
            stmt = db.session.query(AdHocRunHistory.id, AdHocRunHistory.adhoc_id). \
                order_by(AdHocRunHistory.date_start.desc()). \
                subquery()  # 子查询
            history_ids = db.session.query(stmt.c.id).group_by(stmt.c.adhoc_id).all()
            try:
                history_ids = list(zip(*history_ids))[0]
            except IndexError:
                history_ids = []
            filter_conditions['and'].append(('id', 'in', history_ids))

        # 排序
        sort = request.args.get('sort')
        order = request.args.get('order')
        order_by_list = [(sort, order), ]

        # 分页
        offset = request.args.get('offset') or 0
        limit = request.args.get('limit') or 12

        # Compatible with select2
        if request.values.get('type') == 'select2':
            filter_conditions, order_by_list, offset, limit = self.select2_queryset()

        histories, count = AdHocRunHistory.get_queryset(queryset=None,
                                                        filter_conditions=filter_conditions,
                                                        order_by_list=order_by_list,
                                                        offset=offset, limit=limit)
        history_ids = [history.id for history in histories]
        task_dict = db.session.query(AdHocRunHistory.id, Task.name).\
            join(Task, AdHocRunHistory.task_id == Task.id).\
            filter(AdHocRunHistory.id.in_(history_ids)).\
            all()
        task_dict = list2dict(task_dict)

        results = list()
        for history in histories:
            success_hosts = history.success_hosts.keys()
            failed_hosts = history.failed_hosts.keys()
            results.append({
                'id': history.id,
                'task_name': task_dict.get(history.id),
                'date_start': timestamp_obj_to_str(history.date_start),
                'date_finished': timestamp_obj_to_str(history.date_finished),
                'timedelta': str(datetime.timedelta(seconds=history.timedelta)) if history.timedelta else '-',
                'is_finished': history.is_finished,
                'is_success': history.is_success,
                'success_hosts': ' ,'.join(success_hosts),
                'failed_hosts': ' ,'.join(failed_hosts),
                'celery_id': history.celery_id,
            })

        resp = {
            "data": {
                "rows": results,
                "total": count
            }
        }
        return jsonify(resp)


class HistoryListView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'ops-task-list'

    def get(self):
        adhoc = request.args.get('adhoc', default='')
        return render_template('ops/history_list.html', adhoc=adhoc)
